﻿using System;
using System.Collections.Specialized;
using System.Configuration.Provider;
using System.Security.Principal;
using System.Web;
using System.Web.Security;
using SubSonic.Security.Data;

namespace SubSonic.Security{

    public class MembershipProvider : System.Web.Security.MembershipProvider {

        #region PrivateVariables
        private string _ProviderName = String.Empty;
        private MemProviderConfig Configuration = null;
        #endregion

        #region Initialize

        public override void Initialize(string name, NameValueCollection config){
            if (config == null)
                throw new ArgumentNullException("config");

            _ProviderName = name;

            if (_ProviderName == String.Empty)
                _ProviderName = "SubSonicMembershipProvider";

            base.Initialize(name, config);
            
            Configuration = new MemProviderConfig(config);

            if(Configuration == null)
                throw new ProviderException("There is an error with your configuration.");
        }

        #endregion

        #region Properties

        public override string ApplicationName{
            get  { return Configuration.ApplicationName; }
            set  { Configuration.ApplicationName = value; }
        }

        public override bool EnablePasswordReset{
            get { return true; }
        }

        public override bool EnablePasswordRetrieval{
            get { return Configuration.EnablePasswordRetrieval; }
        }

        public override int MaxInvalidPasswordAttempts{
            get { return Configuration.MaxInvalidPasswordAttempts; }
        }

        public override int MinRequiredNonAlphanumericCharacters{
            get { return Configuration.MinRequiredNonAlphanumericCharacters; }
        }

        public override int MinRequiredPasswordLength{
            get { return Configuration.MinRequiredPasswordLength; }
        }

        public override int PasswordAttemptWindow{
            get { return Configuration.PasswordAttemptWindow; }
        }

        public override MembershipPasswordFormat PasswordFormat{
            get { return Configuration.PasswordFormat; }
        }

        public override string PasswordStrengthRegularExpression{
            get { return Configuration.PasswordStength; }
        }

        public override bool RequiresQuestionAndAnswer{
            get { return Configuration.RequiresQuestionAndAnswer; }
        }

        public override bool RequiresUniqueEmail{
            get { return Configuration.RequiredUniqueEmail; }
        }
        #endregion

        #region Single User Functions

        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status){
            if (!SubSonic.Sugar.Validation.IsEmail(email))
            {
                status = MembershipCreateStatus.InvalidEmail;
                return null;
            }

            if (this.RequiresQuestionAndAnswer && (passwordAnswer == String.Empty || passwordQuestion == String.Empty))
            {
                status = MembershipCreateStatus.InvalidQuestion;
                return null;
            }

            if (!Helper.CheckPassword(password, this.MinRequiredNonAlphanumericCharacters, this.MinRequiredPasswordLength, this.PasswordStrengthRegularExpression))
            {
                status = MembershipCreateStatus.InvalidPassword;
                return null;
            }

            User u = new User(User.Columns.Username, username);

            if (u.IsLoaded)
                throw new MembershipCreateUserException(MembershipCreateStatus.DuplicateUserName);


            if(this.RequiresUniqueEmail)
            {
                UserCollection ucol  = new UserCollection().Where(User.Columns.Email, email.ToLower()).Load();
                if(ucol.Count > 0)
                    throw new MembershipCreateUserException(MembershipCreateStatus.DuplicateEmail);
            }

            u.UserID = Guid.NewGuid();
            u.Username = username;
            u.Password = Helper.FormatPassword(password, this.PasswordFormat);
            u.LastLogin = DateTime.Now;
            u.DateCreated = DateTime.Now;
            u.Email = email.ToLower();
            u.SecurityQuestion = passwordQuestion;
            u.SecurityAnswer = passwordAnswer;
            u.IsValid = isApproved;
            u.IsNew = true;
            u.Save();

            status = MembershipCreateStatus.Success;

            return u.MemUserObj(_ProviderName);

        }

        public override bool DeleteUser(string username, bool deleteAllRelatedData){
            if(deleteAllRelatedData){
                User u = new User(User.Columns.Username, username);
                u.DeleteAllRelatedData();
            }

            return ( User.Delete(User.Columns.Username, username) > 0 );
        }

        public override string GetPassword(string username, string answer){

            if (!this.EnablePasswordRetrieval & this.PasswordFormat != MembershipPasswordFormat.Clear)
                throw new ProviderException("You are not able to retrieve the password");
            else{
                User u = new User(User.Columns.Username, username);

                if (u.SecurityAnswer == answer)
                    return u.Password;
                else
                    throw new MembershipPasswordException("The answer you provided is not valid");

            }
        }

        public override MembershipUser GetUser(string username, bool userIsOnline){
            User u = new User(User.Columns.Username, username);

            if (u.IsLoaded)
                return u.MemUserObj(_ProviderName);
            else
                return null;
        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline){
            User u = new User(User.Columns.UserID, (Guid)providerUserKey);
            if (u.IsLoaded)
                return u.MemUserObj(_ProviderName);
            else
                return null;
        }

        public override string GetUserNameByEmail(string email){
            User u = new User(User.Columns.Email, email);

            if (u.IsLoaded)
                return u.Username;
            else
                return null;
        }

        public override string ResetPassword(string username, string answer){
            User u = new User(User.Columns.Username, username);

            int ret = SubSonic.Sugar.Numbers.Random(11111111, 99999999);

            if (u.IsLoaded && u.SecurityAnswer == answer)
            {
                u.Password = Helper.FormatPassword(ret.ToString(), this.PasswordFormat);
                return ret.ToString();
            }
            else
                throw new MembershipPasswordException("The answer you provided is not valid");
        }

        public override bool UnlockUser(string userName){
            User u = new User(User.Columns.Username, userName);

            if (u.IsLoaded)
            {
                u.IsValid = true;
                u.IsNew = false;
                u.Save();
                return true;
            }
            else
                return false;
        }

        public override void UpdateUser(MembershipUser user){
            User u = new User(Data.User.Columns.Username, user.UserName);
            u.Update(user);
        }


        

        public override bool ValidateUser(string username, string password){
            Boolean ret = false;

            IPrincipal principal = SubSonic.Security.SubSonicPrincipal.ValidateUser(username, password, this.PasswordFormat);

            string pf = "";

            switch (this.PasswordFormat){ 
                case MembershipPasswordFormat.Clear:
                    pf = "c";
                    break;
                case MembershipPasswordFormat.Encrypted:
                    pf = "e";
                    break;
                default: 
                    pf = "h";
                    break;
            }

            HttpContext c = HttpContext.Current;

            if (principal != null){
                c.Response.Cookies[Helper.Cookies.cookieUsername].Value = username;
                c.Response.Cookies[Helper.Cookies.cookiePassword].Value = username;
                c.Response.Cookies[Helper.Cookies.cookieCacheUser].Value = username;
                c.Response.Cookies[Helper.Cookies.cookiePasFormat].Value = pf;
                c.User = principal;
                ret = true;
            }
            else
                c.User = SubSonic.Security.SubSonicPrincipal.UnauthenticatedUser();

            c.Cache["ss_User_" + username] = c.User;

            return ret;
        }

        public override bool ChangePassword(string username, string oldPassword, string newPassword){
            User u = new User(User.Columns.Username, username);

            if (u.IsLoaded == false)
                return false;

            bool isValid = Helper.ValidatePassword(oldPassword, u.Password, this.PasswordFormat);

            if (isValid)
                isValid = Helper.CheckPassword(newPassword, this.MinRequiredNonAlphanumericCharacters, this.MinRequiredPasswordLength, this.PasswordStrengthRegularExpression);
            else
                return false;

            if (isValid){
                u.Password = Helper.FormatPassword(newPassword, this.PasswordFormat);
                u.IsNew = false;
                u.Save();
                return true;
            }
            else
                return false;
        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer){
            User u = new User(User.Columns.Username, username);

            if (u.IsLoaded == false)
                return false;

            if (Helper.ValidatePassword(password, u.Password, this.PasswordFormat)){
                u.SecurityQuestion = newPasswordQuestion;
                u.SecurityAnswer = newPasswordAnswer;
                u.IsNew = false;
                u.Save();
                return true;
            }
            else
                return false;
        }
        #endregion
        
        #region GetUsers
        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords){
            Query qry = new Query(Tables.User, "SubSonicProviders");
            qry.PageIndex = pageIndex + 1;
            qry.PageSize = pageSize;
            qry.AddWhere(User.Columns.Email, emailToMatch);
            qry.OrderBy = OrderBy.Asc(User.Columns.Username);

            UserCollection usrcol = new UserCollection();
            usrcol.LoadAndCloseReader(qry.ExecuteReader());

            MembershipUserCollection col = new MembershipUserCollection();
            foreach (User u in usrcol)
                col.Add(u.MemUserObj(_ProviderName));

            totalRecords = col.Count;
            return col;
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords){
            Query qry = new Query(Tables.User, "SubSonicProviders");
            qry.PageIndex = pageIndex + 1;
            qry.PageSize = pageSize;
            qry.AddWhere(User.Columns.Username, usernameToMatch);
            qry.OrderBy = OrderBy.Asc(User.Columns.Username);

            UserCollection usrcol = new UserCollection();
            usrcol.LoadAndCloseReader(qry.ExecuteReader());

            MembershipUserCollection col = new MembershipUserCollection();
            foreach (User u in usrcol)
                col.Add(u.MemUserObj(_ProviderName));

            totalRecords = col.Count;
            return col;
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords){
            Query qry = new Query(Tables.User, "SubSonicProviders");
            qry.PageIndex = pageIndex + 1;
            qry.PageSize = pageSize;
            qry.OrderBy = OrderBy.Asc(User.Columns.Username);

            UserCollection usrcol = new UserCollection();
            usrcol.LoadAndCloseReader(qry.ExecuteReader());

            MembershipUserCollection col = new MembershipUserCollection();
            foreach (User u in usrcol)
                col.Add(u.MemUserObj(_ProviderName));

            totalRecords = col.Count;
            return col;
        }

        public override int GetNumberOfUsersOnline(){
            UserCollection ucol = new UserCollection().Where(User.Columns.LastLogin, Comparison.GreaterOrEquals, DateTime.Now.AddMinutes(-10)).Load();
            return ucol.Count;
        }

        #endregion

    }
}
